Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make return type of broadcast inferrable with heterogeneous arrays #30485

Merged
merged 14 commits into from
Oct 27, 2020

Conversation

nalimilan
Copy link
Member

@nalimilan nalimilan commented Dec 21, 2018

Inference is not able to detect the element type automatically, but we can do it manually since we know promote_typejoin is used for widening.

Fixes #28382. The same changes should be applied to map.

base/broadcast.jl Outdated Show resolved Hide resolved
@@ -360,7 +360,7 @@ end
let f17314 = x -> x < 0 ? false : x
@test eltype(broadcast(f17314, 1:3)) === Int
@test eltype(broadcast(f17314, -1:1)) === Integer
@test eltype(broadcast(f17314, Int[])) == Union{Bool,Int}
@test eltype(broadcast(f17314, Int[])) === Integer
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a minor change which I think can be considered as a bug fix, in the sense that before this PR the element type when the input is empty will never be observed when the array isn't empty (we can only ever observe Int, Bool or Integer). I could change the PR to preserve the existing behavior if we want (e.g. for backports).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh that's interesting. Yeah, here's the current behaviors:

julia> eltype(broadcast(f17314, Int[]))
Union{Bool, Int64}

julia> eltype(broadcast(f17314, Int[1]))
Int64

julia> eltype(broadcast(f17314, Int[-1]))
Bool

julia> eltype(broadcast(f17314, Int[1,-1]))
Integer

The reason for using inference here in the first place is to preserve the performance in the non-empty case. Adding a fourth possible return type defeats such a purpose, so I'm in support of this change.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly - we either need to change the last to match the first, or the first to match the last.

This PR seems the least breaking, and suitable for v1.x. If we ever wanted to consider the other way around maybe that should be a v2.0 change?

@nalimilan nalimilan added performance Must go faster broadcast Applying a function over a collection missing data Base.missing and related functionality labels Dec 21, 2018
@mbauman mbauman added needs news A NEWS entry is required for this change minor change Marginal behavior change acceptable for a minor release labels Dec 21, 2018
Copy link
Member

@mbauman mbauman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I cannot meaningfully review the way in which you achieve the change in behaviors here, but I approve of the result and think this is a minor change worth making.

@vtjnash
Copy link
Member

vtjnash commented Feb 5, 2019

I'm still not sure. I'm not really sure that Base._return_type is reporting the right answer here, and I'm not sure that various hackery to make the answer more contorted is really the right solution that.

@nalimilan
Copy link
Member Author

I'm still not sure. I'm not really sure that Base._return_type is reporting the right answer here, and I'm not sure that various hackery to make the answer more contorted is really the right solution that.

What do you mean? Why would return_type be incorrect? If it's imprecise in some cases, that's OK. Also it's already used, in particular when the result is empty. This PR doesn't really make things worse in that regard as it only affects inference.

Or do you have a better proposal? I agree it would be nice if the compiler did that automatically, but until it does we really need to avoid the inference failure that broadcast creates with Union{T,Missing}.

@vtjnash vtjnash added the forget me not PRs that one wants to make sure aren't forgotten label Dec 17, 2019
@vtjnash
Copy link
Member

vtjnash commented Aug 12, 2020

Upon much reflection, I now think this is actually sensible, and does actually help slightly to decouple inference from the result here, as Core.Compiler.return_types returns lists of types as a Union (possible inside a Tuple), and for usability, that shouldn't actually ever be consumed directly, but instead converted into a proper simplified type (as done here). I think there's some implication there also that users of return_types should never use anything but typejoin to widen their types, but that's just a predicability/quality improvement not correctness. And possibly return_types should do be doing this itself directly (to prevent users from depending on the specific complexities of inference in ways that can't even be seen at runtime), but this seems at least reasonable as an improvement to this consumer.

base/broadcast.jl Outdated Show resolved Hide resolved
base/broadcast.jl Outdated Show resolved Hide resolved
base/broadcast.jl Outdated Show resolved Hide resolved
base/broadcast.jl Show resolved Hide resolved
base/broadcast.jl Outdated Show resolved Hide resolved
Inference is not able to detect the element type automatically, but we can do it manually
since we know promote_typejoin is used for widening.
@nalimilan
Copy link
Member Author

nalimilan commented Aug 13, 2020

Thanks for the review. I'm amazed I didn't get more things wrong. :-p
I've added code to compute N for Vararg, let me know if it's correct.

Unfortunately, after rebasing against current master, the return type is only inferred as AbstractArray{...} while on the previous state of the PR if was inferred as a concrete Array{...} type. This is due to the fact that copyto_nonleaf! is now inferred as Any while before (on 1.5.0 it was still the case) it was inferred as Array, as indicated by e.g. @code_warntype Base.Broadcast.copyto_nonleaf!([1], Base.Broadcast.Broadcasted(+, ([1,2], [1,missing])), [1, 2], 1, 1). This PR is still an improvement so I could relax the tests for now until we find the fix.

@nalimilan
Copy link
Member Author

Good to go @vtjnash?

base/broadcast.jl Outdated Show resolved Hide resolved
base/broadcast.jl Outdated Show resolved Hide resolved
base/broadcast.jl Outdated Show resolved Hide resolved
@JeffBezanson
Copy link
Member

IIUC, only the type assertion is needed for the inference improvement, and it seems to me that is much easier?

@nalimilan
Copy link
Member Author

IIUC, only the type assertion is needed for the inference improvement, and it seems to me that is much easier?

AFAICT all changes are needed. promote_typejoin_union is needed to compute e.g. Integer when the function returns either Int or Bool. Otherwise the computed eltype will be Union{Int, Bool}, which isn't a supertype of Integer, so the assertion will fail.

What could be avoided is the minor change of eltype from Union{Int, Bool} to Integer when the input is empty (#30485 (comment)), but that doesn't make the PR more complex.

@nalimilan
Copy link
Member Author

Bump. This is going to miss 1.6 (meaning it will reach its second anniversary without being released...).

@nalimilan
Copy link
Member Author

Thanks @vtjnash and @JeffBezanson. I'll merge tomorrow if nobody objects (FreeBSD failure seems unrelated).

Comment on lines +956 to +978
@test_broken Core.Compiler.return_type(broadcast, Tuple{typeof(+), Vector{Int},
Vector{Union{Float64, Missing}}}) ==
Vector{<:Union{Float64, Missing}}
@test Core.Compiler.return_type(broadcast, Tuple{typeof(+), Vector{Int},
Vector{Union{Float64, Missing}}}) ==
AbstractVector{<:Union{Float64, Missing}}
@test isequal([1, 2] + [3.0, missing], [4.0, missing])
@test_broken Core.Compiler.return_type(+, Tuple{Vector{Int},
Vector{Union{Float64, Missing}}}) ==
Vector{<:Union{Float64, Missing}}
@test Core.Compiler.return_type(+, Tuple{Vector{Int},
Vector{Union{Float64, Missing}}}) ==
AbstractVector{<:Union{Float64, Missing}}
@test_broken Core.Compiler.return_type(+, Tuple{Vector{Int},
Vector{Union{Float64, Missing}}}) ==
Vector{<:Union{Float64, Missing}}
@test isequal(tuple.([1, 2], [3.0, missing]), [(1, 3.0), (2, missing)])
@test_broken Core.Compiler.return_type(broadcast, Tuple{typeof(tuple), Vector{Int},
Vector{Union{Float64, Missing}}}) ==
Vector{<:Tuple{Int, Any}}
@test Core.Compiler.return_type(broadcast, Tuple{typeof(tuple), Vector{Int},
Vector{Union{Float64, Missing}}}) ==
AbstractVector{<:Tuple{Int, Any}}
Copy link
Member

@simeonschaub simeonschaub Feb 12, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#39618 seems to fix these tests. @nalimilan Could that mean that this workaround might not even be needed anymore? Or does it maybe just fix this particular example?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah then it's great! Actually all the work I did in this PR had not effect for now due to this inference failure (a regression due to changes to reduce compile time introduced since 1.5). So if you can replace these @test_broken with @test then it's perfect!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, cool. Thanks for checking!

@simeonschaub simeonschaub removed the forget me not PRs that one wants to make sure aren't forgotten label May 29, 2021
nalimilan added a commit that referenced this pull request Aug 29, 2021
Inference is not able to detect the element type automatically,
but we can do it manually since we know promote_typejoin is used for widening.
This is similar to the approach used for `broadcast` at #30485.
nalimilan added a commit that referenced this pull request Aug 29, 2021
Inference is not able to detect the element type automatically,
but we can do it manually since we know promote_typejoin is used for widening.
This is similar to the approach used for `broadcast` at #30485.
nalimilan added a commit that referenced this pull request Sep 1, 2021
Inference is not able to detect the element type automatically,
but we can do it manually since we know promote_typejoin is used for widening.
This is similar to the approach used for `broadcast` at #30485.
KristofferC pushed a commit that referenced this pull request Sep 15, 2021
Inference is not able to detect the element type automatically,
but we can do it manually since we know promote_typejoin is used for widening.
This is similar to the approach used for `broadcast` at #30485.

(cherry picked from commit 49e3aec)
KristofferC pushed a commit that referenced this pull request Sep 15, 2021
Inference is not able to detect the element type automatically,
but we can do it manually since we know promote_typejoin is used for widening.
This is similar to the approach used for `broadcast` at #30485.

(cherry picked from commit 49e3aec)
LilithHafner pushed a commit to LilithHafner/julia that referenced this pull request Feb 22, 2022
…ng#42046)

Inference is not able to detect the element type automatically,
but we can do it manually since we know promote_typejoin is used for widening.
This is similar to the approach used for `broadcast` at JuliaLang#30485.
LilithHafner pushed a commit to LilithHafner/julia that referenced this pull request Mar 8, 2022
…ng#42046)

Inference is not able to detect the element type automatically,
but we can do it manually since we know promote_typejoin is used for widening.
This is similar to the approach used for `broadcast` at JuliaLang#30485.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
broadcast Applying a function over a collection minor change Marginal behavior change acceptable for a minor release missing data Base.missing and related functionality needs news A NEWS entry is required for this change performance Must go faster
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Poor inference of [1, 2] + [3.0, missing]`
6 participants